var jfJQMSwipeable = (function($, document, window) {

  /* PRIVATE SCOPE */

  var $document = $(document),
      $window = $(window),
      clickEvent = 'click';


  function initConfig() {
    var config = this.config || {};
    
    try {
      config = $.extend(true, $.parseJSON(this.$el.attr('data-swipeable')), config);
    }
    catch (e) {}

    var $children = this.$el.find('>*');
    config.numElements = $children.length;

    if (!config.currentPosition) {
      var $activeChild = this.$el.find('[data-swipeable-active]');
      if ($activeChild.length) {
        config.currentPosition = $children.index($activeChild);
      }
      else {
        config.currentPosition = 0;
      }
    }
  
    this.config = config;
  }
      
  function initWrapper() {
    
    var $wrapper = this.getWrapperEl();
    if (!$wrapper.length) {
      var $innerWrapper = $('<div class="jfJQMSwipeableInnerWrapper">');
      this.$el.wrap($innerWrapper);
      $innerWrapper = this.$el.parent();
      $wrapper = $('<div class="jfJQMSwipeableWrapper">');
      $innerWrapper.wrap($wrapper);
      $wrapper = $innerWrapper.parent();
    }

    if (!this.getConfig('loop')) {
      $wrapper.addClass('noLoop');
    }
    $wrapper.closest('li.ui-li').addClass('jfJQMSwipeableInside');
  }
  
  function initControls() {

    var $wrapper = this.getWrapperEl();
    $wrapper.prepend($('<div class="counter"><span class="visual"></span><span class="literal"></span></div>'));
    
    $left = $('<div class="pager left" data-jfJQMSwipeable-direction="right"><span></span></div>');
    $right = $('<div class="pager right" data-jfJQMSwipeable-direction="left"><span></span></div>');
    
    $wrapper.prepend($left);
    $wrapper.prepend($right);

    var self = this;
    $wrapper.find('[data-jfJQMSwipeable-direction=right]').bind(clickEvent, function(e) { e.stopImmediatePropagation(); e.preventDefault(); highlightClickedElement($(this)); self.prev(); });
    $wrapper.find('[data-jfJQMSwipeable-direction=left]').bind(clickEvent, function(e) { e.stopImmediatePropagation(); e.preventDefault(); highlightClickedElement($(this)); self.next(); });
  }
  
  function highlightClickedElement($el) {
    $el.addClass('active');
    setTimeout(function() { $el.removeClass('active'); }, 150);
  }
  
  function updateControls() {
    var $wrapper = this.getWrapperEl();
    var $counter = $wrapper.find('.counter');
    
    var current = this.getConfig('currentPosition'),
        count   = this.getConfig('numElements');

    $wrapper.removeClass('first last');
    if (current === 0) {
      setTimeout(function() { $wrapper.addClass('first'); }, 150);
    }
    else if (current === count-1) {
      setTimeout(function() { $wrapper.addClass('last'); }, 150);
    }
    
    var $counterLiteral = $counter.find('.literal');
    $counterLiteral.html((current+1)+' / '+count);
    
    var segmentWidth = 17;
    var maxSegments = 9;
    
    if (count > maxSegments) {
      $wrapper.addClass('hideVisualCounter');
    }
    else {
      $wrapper.removeClass('hideVisualCounter').
        find('span.visual').css({
          width: segmentWidth*count,
          backgroundPosition: (maxSegments*segmentWidth*-1)+(segmentWidth*(current+1))
        });
    }
  }
  
  function initSwipe() {
    if (this.swipe) {
      this.swipe.setup();
    }
    else {
      var options = this.getConfig('swipeOptions', {});
      
      if (!this.getConfig('loop')) {
        options.continuous = false;
      }
      
      options.startSlide = this.getConfig('currentPosition');
      
      options.callback = swipedTo.bind(this);
    
      this.swipe = new Swipe(this.getWrapperEl().find('.jfJQMSwipeableInnerWrapper')[0], options); 
      this.$el.bind('swipePrev', this.prev.bind(this));
      this.$el.bind('swipeNext', this.next.bind(this));
    }
  }
  
  function swipedTo(index) {
    index = parseInt(index, 10);
    if (this.config.scrollToTop) {
      var configScrollToTop = this.config.scrollToTop;
      var top = this.getWrapperEl().offset().top;
      switch (typeof configScrollToTop) {
        case 'number':
          top = Math.max(0, top+configScrollToTop);
          break;
        case 'string':
          if (configScrollToTop === 'window_top') {
            top = 0;
          }
          break;
      }

      if ($.mobile) {
        $.mobile.silentScroll(top);
      }
      else {
        window.scroll(top);
      }
    }

    var path;

    var crossedBoundary = false;
    var lastIndex = this.config.currentPosition;
    var maxIndex = this.getConfig('numElements')-1;
    if (lastIndex - index === 1 || lastIndex - index === maxIndex*-1) {
      path = 'prev';
    }
    else if (lastIndex - index === -1 || lastIndex - index === maxIndex) {
      path = 'next';
    }
    else {
      path = 'direct';
    }

    if (path === 'next' && index === 0 && lastIndex === maxIndex) {
      crossedBoundary = true;
    }
    else if (path === 'prev' && index === maxIndex && lastIndex === 0) {
      crossedBoundary = true;
    }

    this.config.currentPosition = index;
    updateControls.call(this);
    
    updateHeight.call(this);
    this.swipe.setup();
    this.$el.trigger('swiped', [this, { path: path, crossedBoundary: crossedBoundary }]);
  }
  
  function updateHeight() {
    if (!this.config.autoHeight) {
      return;
    }

    var $el = this.$el;
    
    var $currentElement = $el.find('>:nth-child('+(this.config.currentPosition+1)+')');
    getRealHeightOfElement($currentElement, function(height) {
      height = height + parseInt($currentElement.css('margin-top'), 10); parseInt($currentElement.css('margin-bottom'), 10);
      $el.css({ '-webkit-transition-property': 'none', 'height': height+'px'});
      this.swipe.setup(); //recalculate widths in case that scrollbar visibility changed due to change of element height
      setTimeout(function() { $el.css('-webkit-transition-property', 'initial'); }, 20);
    }.bind(this));
  }
  
  function getRealHeightOfElement($element, callback) {
    var $clone = $element.clone().
                    attr('id', false).
                    css({'position':'absolute','visibility':'hidden','display':'block'});
    $element.parent().append($clone);
    setTimeout(function() {
      var height = $clone.height();
      $clone.remove();
      callback(height);
    }, 500);
  }

  function debounce(debounceTimerCallback, debounceTimerCallbackTimeout) {
    var debounceTimer;
    return function() {
        clearTimeout(debounceTimer); 
        debounceTimer = setTimeout(debounceTimerCallback.bind(this, [].slice.apply(arguments)), debounceTimerCallbackTimeout); 
    };
  };
  
  /* PUBLIC SCOPE */
  
  function jfJQMSwipeable(el) {
    this.el = el;
    this.$el = $(el);

    this.$el.find('script').filter(function() {
      return !$(this).attr('type') || $(this).attr('type').toLowerCase() === 'javascript';
    }).remove(); //remove inline scripts as the would get re-executed when swiping

    this.$el.data('jfJQMSwipeable', this);

    initConfig.call(this);
    initWrapper.call(this);

    setTimeout(function() { 
      initSwipe.call(this);
      initControls.call(this);
      updateControls.call(this);
      updateHeight.call(this);
      setTimeout(updateHeight.bind(this), 2000);
    }.bind(this), 100);   

  }

  jfJQMSwipeable.prototype = {

    refresh: function() {
      initConfig.call(this);
      initSwipe.call(this);
      updateControls.call(this);
      updateHeight.call(this);
      if (this.isLast()) {
        setTimeout(this.prev.bind(this), 200);
        setTimeout(this.next.bind(this), 400);
      }
      else {
        setTimeout(this.next.bind(this), 200);
        setTimeout(this.prev.bind(this), 400);
      }
    },

    updateHeight: updateHeight,
  
    getWrapperEl: function() {
      return this.$el.closest('div.jfJQMSwipeableWrapper');
    },
    
    getCurrentEl: function() {
      return this.$el.find('[data-index='+this.getConfig('currentPosition')+']');
    },
    
    isFirst: function() {
      return this.getConfig('currentPosition') === 0;
    },
    isLast: function() {
      return this.getConfig('currentPosition') === this.getConfig('numElements') - 1;
    },
    
    prev: function() {
      if (this.getConfig('currentPosition') === 0) {
        if (this.getConfig('loop')) {
          var lastIndex = this.getConfig('numElements')-1;
          this.swipe.slide(lastIndex, 100);
        }
      }
      else {
        this.swipe.prev();
      }
    },

    next: function() {
      if (this.getConfig('currentPosition') === this.getConfig('numElements')-1) {
        if (this.getConfig('loop')) {
          this.swipe.slide(0, 100);
        }
      }
      else {
        this.swipe.next();
      }
    },
    
    to: function(index) {
      if (index === this.getConfig('currentPosition')) {
        setTimeout(swipedTo.bind(this, index), 0);
      }
      else {
        this.setConfig('currentPosition', index); //manually update currentPosition to avoid that "path info" (prev/next, crossedBoundary) gets calculated with wrong values when jumping directly to a given index
        this.swipe.slide(index, 100);
      }
      
    },
    
    getConfig: function(key, defaultValue) {
      var value = this.config[key];
      if (typeof value === 'undefined') {
        value = defaultValue;
      }
      else if (typeof value === 'object') {
        value = $.extend(true, {}, value); //return copy of object to avoid side-effects
      }
      
      return value;
    },

    setConfig: function(key, value) {
      this.config[key] = value;
    }
  };
  

  
  /* AUTO-INIT */

  function initPage($page) {
    $page.find("[data-swipeable]").each(function(idx, el) {
      var swipeable = $(el).data('jfJQMSwipeable');
      if (swipeable) {
        swipeable.refresh();
      }
      else {
        new jfJQMSwipeable(el);
      }
    });
  }

  if ($.mobile) {
    $document.on('pageshow create refresh', "div:jqmData(role='page')", debounce(function(e) { initPage($(this)); }, 800));
  }
  else {
    $document.ready(function(e) { initPage($document); });
  }

  return jfJQMSwipeable;

})(jQuery, document, window);